/*
 * @Author: czy
 * @Date: 2021-05-19 21:29:37
 * @LastEditTime: 2021-07-12 22:12:51
 */
#include "stddef.h"
#include "stdbool.h"
#include "string.h"
#include "vsprintf.h"

#include "sys/container.h"
#include "sys/system.h"
#include "sys/type.h"
#include "sys/const.h"

#include "../lib/input.h"
#include "../lib/output.h"

static Deque history_command = newDeque(his, 30);

typedef struct
{
    char *head;
    char *tail;
    char *current;
} instruction;

/**
 * @description: Move the cursor left/right several bits.
 * @param offset    Offset.
 */
static void cursor_offset(int offset)
{
    vga_offset(offset);
    uart_offset(offset);
}

/**
 * @description: Insert a character at the cursor of the current instruction.
 * @param ins   Instruction.
 * @param ch    Character.
 */
static void insert(instruction *ins, char ch)
{
    if (ch >= 32 && ch <= 126)
    {
        strinsert(ins->head, ins->current - ins->head, ch);
        write(ins->current);
        cursor_offset(-1 * (strlen(ins->current) - 1));
        ins->tail++;
        ins->current++;
    }
}

/**
 * @description: Delete a character at the cursor of the current instruction.
 * @param ins   Instruction.
 */
static void delete (instruction *ins)
{
    if (ins->current > ins->head)
    {
        putc(127);
        strdelete(ins->head, ins->current - ins->head - 1);
        ins->current--;
        char temp[100] = "";
        sprintf(temp, "%s \b", ins->current);
        write(temp);
        cursor_offset(-1 * strlen(ins->current));
        ins->tail--;
    }
}

/**
 * @description: Cursor movement.
 * @param ins       Instruction.
 * @param direction LEFT or RIGHT.
 */
static void move(instruction *ins, int direction)
{
    if (direction == LEFT && (ins->current > ins->head))
    {
        ins->current--;
        cursor_offset(-1);
    }
    if (direction == RIGHT && (ins->current < ins->tail))
    {
        ins->current++;
        cursor_offset(1);
    }
}

/**
 * @description: Complete the current instruction.
 * @param ins   Instruction.
 */
static int complete(instruction *ins, const char *promrt)
{
    char names[20][20];
    Map *cmd = (Map *)(getenv("command"));
    command data;
    memset(&data, '\0', sizeof(data));
    int length = 0;
    while (cmd->values(cmd, &data) != NULL)
    {
        strcpy(names[length], data.name);
        length++;
    }

    if (ins->head == NULL || ins->current != ins->tail)
    {
        return -1;
    }
    int num, count = 0;
    char *words[WORD_SIZE];
    int match[WORD_SIZE];
    char word[20];
    char temp[80];
    char buf[100] = "";
    strcpy(temp, ins->head);
    deblank(temp);
    num = split(words, temp, " ");
    strcpy(word, words[num - 1]);
    for (int i = 0; i < length; i++)
    {
        if (strindex(word, names[i]) == 0)
        {
            match[count++] = i;
        }
    }
    if (count == 1)
    {
        int len = strlen(word);
        if (len != strlen(names[match[0]]))
        {
            write(names[match[0]] + len);
            strcat(ins->head, names[match[0]] + len);
            ins->current += (strlen(names[match[0]]) - len);
            ins->tail = ins->current;
        }
        return match[0];
    }
    else if (count > 1)
    {
        putc('\n');
        for (int i = 0; i < count; i++)
        {
            sprintf(buf, "%s\t", names[match[i]]);
            write(buf);
            strzero(buf, 100);
        }
        sprintf(buf, "\n%s%s", promrt, ins->head);
        write(buf);
        return -1;
    }
}

/**
 * @description: Replace the current instruction.
 * @param ins   Instruction.
 * @param str   String.
 */
static void replace(instruction *ins, char *str)
{
    for (int i = 0; i < ins->tail - ins->current; i++)
    {
        putc(' ');
    }
    for (int i = 0; i < ins->tail - ins->head; i++)
    {
        putc(127);
    }
    if (*str == '\0')
    {
        strzero(ins->head, 2);
    }
    else
    {
        strcpy(ins->head, str);
    }
    write(ins->head);
    ins->tail = ins->head + strlen(str);
    ins->current = ins->tail;
}

/**
 * @description: Get the history command.
 * @param ins       Instruction.
 * @param direction UP or DOWN.

 */
static void history(instruction *ins, int direction)
{
    his *history_cmd;
    // DOWN
    if (direction == false)
    {
        history_cmd = (his *)history_command.next(&history_command);
    }
    // UP
    else
    {
        history_cmd = (his *)history_command.previous(&history_command);
    }
    if (history_cmd != NULL)
    {
        replace(ins, history_cmd->data);
    }
    else
    {
        if (direction == false)
        {
            replace(ins, "");
        }
    }
}

/**
 * @description: Submit the current instruction to the shell.
 * @param ins   Instruction.
 */
static char *submit(instruction *ins)
{
    deblank(ins->head);
    his history_cmd;
    strcpy(history_cmd.data, ins->head);
    history_command.frpush(&history_command, history_cmd);
    putc('\n');
    return ins->head;
}

/**
 * @description: Decode the input character.
 * @param ch    Input character.
 * @return Decoded character.
 */
static int decode(char ch)
{
    if (ch == 27)
    {
        if (getc() == 91)
        {
            switch (getc())
            {
            case 65:
                return UP_DIRECTION;
            case 66:
                return DOWN_DIRECTION;
            case 67:
                return RIGHT_DIRECTION;
            case 68:
                return LEFT_DIRECTION;
            default:
                return 0;
            }
        }
    }
    return ch;
}

/**
 * @description: Read a line from the input device.
 * @param str       The first address of the input string.
 * @param promrt    Input prompt.
 * @return The first address of the input string.
 */
char *read(char *str, const char *promrt)
{
    printk(promrt);
    char ch;
    instruction ins = {str, str, str};
    while (true)
    {
        ch = getc();
        switch (decode(ch))
        {
        case '\t':
            complete(&ins, promrt);
            break;
        case 127:
            delete (&ins);
            break;
        case LEFT_DIRECTION:
            move(&ins, LEFT);
            break;
        case RIGHT_DIRECTION:
            move(&ins, RIGHT);
            break;
        case UP_DIRECTION:
            history(&ins, true);
            break;
        case DOWN_DIRECTION:
            history(&ins, false);
            break;
        case '\r':
            return submit(&ins);
        default:
            insert(&ins, ch);
            break;
        }
    }
}

/**
 * @description: Returns a pointer to the history input structure.
 * @return Pointer.
 */
void *his_pointer(void)
{
    return (void *)(&history_command);
}
